home *** CD-ROM | disk | FTP | other *** search
/ PC Expert 29 / Pce29cd.iso / RUNIMAGE / DELPHI40 / DEMOS / Virtual Listview / vlistview.pas < prev   
Pascal/Delphi Source File  |  1998-06-16  |  17KB  |  658 lines

  1. unit VListView;
  2.  
  3. interface
  4.  
  5. uses
  6.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  7.   ComCtrls, StdCtrls, ToolWin, ShlObj, ImgList, Menus;
  8.  
  9. type
  10.   PShellItem = ^TShellItem;
  11.   TShellItem = record
  12.     FullID,
  13.     ID: PItemIDList;
  14.     Empty: Boolean;
  15.     DisplayName,
  16.     TypeName: string;
  17.     ImageIndex,
  18.     Size,
  19.     Attributes: Integer;
  20.     ModDate: string;
  21.   end;
  22.  
  23.   TForm1 = class(TForm)
  24.     ListView: TListView;
  25.     CoolBar1: TCoolBar;
  26.     ToolBar2: TToolBar;
  27.     ToolbarImages: TImageList;
  28.     btnBrowse: TToolButton;
  29.     btnLargeIcons: TToolButton;
  30.     btnSmallIcons: TToolButton;
  31.     btnList: TToolButton;
  32.     btnReport: TToolButton;
  33.     cbPath: TComboBox;
  34.     ToolButton3: TToolButton;
  35.     PopupMenu1: TPopupMenu;
  36.     btnBack: TToolButton;
  37.     procedure FormCreate(Sender: TObject);
  38.     procedure ListViewData(Sender: TObject; Item: TListItem);
  39.     procedure btnBrowseClick(Sender: TObject);
  40.     procedure cbPathKeyDown(Sender: TObject; var Key: Word;
  41.       Shift: TShiftState);
  42.     procedure cbPathClick(Sender: TObject);
  43.     procedure btnLargeIconsClick(Sender: TObject);
  44.     procedure ListViewDblClick(Sender: TObject);
  45.     procedure ListViewDataHint(Sender: TObject; StartIndex,
  46.       EndIndex: Integer);
  47.     procedure ListViewKeyDown(Sender: TObject; var Key: Word;
  48.       Shift: TShiftState);
  49.     procedure ListViewDataFind(Sender: TObject; Find: TItemFind;
  50.       const FindString: String; const FindPosition: TPoint;
  51.       FindData: Pointer; StartIndex: Integer; Direction: TSearchDirection;
  52.       Wrap: Boolean; var Index: Integer);
  53.     procedure ListViewCustomDrawItem(Sender: TCustomListView;
  54.       Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
  55.     procedure ListViewCustomDrawSubItem(Sender: TCustomListView;
  56.       Item: TListItem; SubItem: Integer; State: TCustomDrawState;
  57.       var DefaultDraw: Boolean);
  58.     procedure btnBackClick(Sender: TObject);
  59.   private
  60.     FPIDL: PItemIDList;
  61.     FIDList: TList;
  62.     FIShellFolder,
  63.     FIDesktopFolder: IShellFolder;
  64.     FPath: string;
  65.     procedure SetPath(const Value: string); overload;
  66.     procedure SetPath(ID: PItemIDList); overload;
  67.     procedure PopulateIDList(ShellFolder: IShellFolder);
  68.     procedure ClearIDList;
  69.     procedure CheckShellItems(StartIndex, EndIndex: Integer);
  70.     function  ShellItem(Index: Integer): PShellItem;
  71.   end;
  72.  
  73. var
  74.   Form1: TForm1;
  75.  
  76. implementation
  77.  
  78. {$R *.DFM}
  79.  
  80. uses ShellAPI, ActiveX, ComObj, CommCtrl, FileCtrl;
  81.  
  82. //PIDL MANIPULATION
  83.  
  84. procedure DisposePIDL(ID: PItemIDList);
  85. var
  86.   Malloc: IMalloc;
  87. begin
  88.   if ID = nil then Exit;
  89.   OLECheck(SHGetMalloc(Malloc));
  90.   Malloc.Free(ID);
  91. end;
  92.  
  93. function CopyITEMID(Malloc: IMalloc; ID: PItemIDList): PItemIDList;
  94. begin
  95.   Result := Malloc.Alloc(ID^.mkid.cb + SizeOf(ID^.mkid.cb));
  96.   CopyMemory(Result, ID, ID^.mkid.cb + SizeOf(ID^.mkid.cb));
  97. end;
  98.  
  99. function NextPIDL(IDList: PItemIDList): PItemIDList;
  100. begin
  101.   Result := IDList;
  102.   Inc(PChar(Result), IDList^.mkid.cb);
  103. end;
  104.  
  105. function GetPIDLSize(IDList: PItemIDList): Integer;
  106. begin
  107.   Result := 0;
  108.   if Assigned(IDList) then
  109.   begin
  110.     Result := SizeOf(IDList^.mkid.cb);
  111.     while IDList^.mkid.cb <> 0 do
  112.     begin
  113.       Result := Result + IDList^.mkid.cb;
  114.       IDList := NextPIDL(IDList);
  115.     end;
  116.   end;
  117. end;
  118.  
  119.  
  120. procedure StripLastID(IDList: PItemIDList);
  121. var
  122.   MarkerID: PItemIDList;
  123. begin
  124.   MarkerID := IDList;
  125.   if Assigned(IDList) then
  126.   begin
  127.      while IDList.mkid.cb <> 0 do
  128.     begin
  129.       MarkerID := IDList;
  130.       IDList := NextPIDL(IDList);
  131.     end;
  132.     MarkerID.mkid.cb := 0;
  133.   end;
  134. end;
  135.  
  136. function CreatePIDL(Size: Integer): PItemIDList;
  137. var
  138.   Malloc: IMalloc;
  139.   HR: HResult;
  140. begin
  141.   Result := nil;
  142.  
  143.   HR := SHGetMalloc(Malloc);
  144.   if Failed(HR) then
  145.     Exit;
  146.  
  147.   try
  148.     Result := Malloc.Alloc(Size);
  149.     if Assigned(Result) then
  150.       FillChar(Result^, Size, 0);
  151.   finally
  152.   end;
  153. end;
  154.  
  155. function CopyPIDL(IDList: PItemIDList): PItemIDList;
  156. var
  157.   Size: Integer;
  158. begin
  159.   Size := GetPIDLSize(IDList);
  160.   Result := CreatePIDL(Size);
  161.   if Assigned(Result) then
  162.     CopyMemory(Result, IDList, Size);
  163. end;
  164.  
  165. function ConcatPIDLs(IDList1, IDList2: PItemIDList): PItemIDList;
  166. var
  167.   cb1, cb2: Integer;
  168. begin
  169.   if Assigned(IDList1) then
  170.     cb1 := GetPIDLSize(IDList1) - SizeOf(IDList1^.mkid.cb)
  171.   else
  172.     cb1 := 0;
  173.  
  174.   cb2 := GetPIDLSize(IDList2);
  175.  
  176.   Result := CreatePIDL(cb1 + cb2);
  177.   if Assigned(Result) then
  178.   begin
  179.     if Assigned(IDList1) then
  180.       CopyMemory(Result, IDList1, cb1);
  181.     CopyMemory(PChar(Result) + cb1, IDList2, cb2);
  182.   end;
  183. end;
  184.  
  185. //SHELL FOLDER ITEM INFO
  186.  
  187. function GetDisplayName(ShellFolder: IShellFolder; PIDL: PItemIDList;
  188.                         ForParsing: Boolean): string;
  189. var
  190.   StrRet: TStrRet;
  191.   P: PChar;
  192.   Flags: Integer;
  193. begin
  194.   Result := '';
  195.   if ForParsing then
  196.     Flags := SHGDN_FORPARSING
  197.   else
  198.     Flags := SHGDN_NORMAL;
  199.  
  200.   ShellFolder.GetDisplayNameOf(PIDL, Flags, StrRet);
  201.   case StrRet.uType of
  202.     STRRET_CSTR:
  203.       SetString(Result, StrRet.cStr, lStrLen(StrRet.cStr));
  204.     STRRET_OFFSET:
  205.       begin
  206.         P := @PIDL.mkid.abID[StrRet.uOffset - SizeOf(PIDL.mkid.cb)];
  207.         SetString(Result, P, PIDL.mkid.cb - StrRet.uOffset);
  208.       end;
  209.     STRRET_WSTR:
  210.       Result := StrRet.pOleStr;
  211.   end;
  212. end;
  213.  
  214. function GetShellImage(PIDL: PItemIDList; Large, Open: Boolean): Integer;
  215. var
  216.   FileInfo: TSHFileInfo;
  217.   Flags: Integer;
  218. begin
  219.   FillChar(FileInfo, SizeOf(FileInfo), #0);
  220.   Flags := SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_ICON;
  221.   if Open then Flags := Flags or SHGFI_OPENICON;
  222.   if Large then Flags := Flags or SHGFI_LARGEICON
  223.   else Flags := Flags or SHGFI_SMALLICON;
  224.   SHGetFileInfo(PChar(PIDL),
  225.                 0,
  226.                 FileInfo,
  227.                 SizeOf(FileInfo),
  228.                 Flags);
  229.   Result := FileInfo.iIcon;
  230. end;
  231.  
  232. function IsFolder(ShellFolder: IShellFolder; ID: PItemIDList): Boolean;
  233. var
  234.   Flags: UINT;
  235. begin
  236.   Flags := SFGAO_FOLDER;
  237.   ShellFolder.GetAttributesOf(1, ID, Flags);
  238.   Result := SFGAO_FOLDER and Flags <> 0;
  239. end;
  240.  
  241.  
  242. function ListSortFunc(Item1, Item2: Pointer): Integer;
  243. begin
  244.   Result := SmallInt(Form1.FIShellFolder.CompareIDs(
  245.                   0,
  246.                   PShellItem(Item1).ID,
  247.                   PShellItem(Item2).ID
  248.             ));
  249. end;
  250.  
  251. {TForm1}
  252.  
  253. //GENERAL FORM METHODS
  254.  
  255. procedure TForm1.FormCreate(Sender: TObject);
  256. var
  257.   FileInfo: TSHFileInfo;
  258.   ImageListHandle: THandle;
  259.   NewPIDL: PItemIDList;
  260. begin
  261.   OLECheck(SHGetDesktopFolder(FIDesktopFolder));
  262.   FIShellFolder := FIDesktopFolder;
  263.   FIDList := TList.Create;
  264.   ImageListHandle := SHGetFileInfo('C:\',
  265.                            0,
  266.                            FileInfo,
  267.                            SizeOf(FileInfo),
  268.                            SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
  269.   SendMessage(ListView.Handle, LVM_SETIMAGELIST, LVSIL_SMALL, ImageListHandle);
  270.  
  271.   ImageListHandle := SHGetFileInfo('C:\',
  272.                            0,
  273.                            FileInfo,
  274.                            SizeOf(FileInfo),
  275.                            SHGFI_SYSICONINDEX or SHGFI_LARGEICON);
  276.  
  277.   SendMessage(ListView.Handle, LVM_SETIMAGELIST, LVSIL_NORMAL, ImageListHandle);
  278.   OLECheck(
  279.     SHGetSpecialFolderLocation(
  280.       Application.Handle,
  281.       CSIDL_DRIVES,
  282.       NewPIDL)
  283.   );
  284.   SetPath(NewPIDL);
  285.   ActiveControl := cbPath;
  286.   cbPath.SelStart := 0;
  287.   cbPath.SelLength := Length(cbPath.Text);
  288. end;
  289.  
  290. procedure TForm1.btnBrowseClick(Sender: TObject);
  291. var
  292.   S: string;
  293. begin
  294.   S := '';
  295.   if SelectDirectory('Select Directory', '', S) then
  296.     SetPath(S);
  297. end;
  298.  
  299. procedure TForm1.cbPathKeyDown(Sender: TObject; var Key: Word;
  300.   Shift: TShiftState);
  301. begin
  302.   if Key = VK_RETURN then
  303.   begin
  304.     if cbPath.Text[Length(cbPath.Text)] = ':' then
  305.       cbPath.Text := cbPath.Text + '\'; 
  306.     SetPath(cbPath.Text);
  307.     Key := 0;
  308.   end;
  309. end;
  310.  
  311. procedure TForm1.cbPathClick(Sender: TObject);
  312. var
  313.   I: Integer;
  314. begin
  315.   I := cbPath.Items.IndexOf(cbPath.Text);
  316.   if I >= 0 then
  317.     SetPath(PItemIDList(cbPath.Items.Objects[I]))
  318.   else
  319.     SetPath(cbPath.Text);
  320. end;
  321.  
  322. procedure TForm1.btnLargeIconsClick(Sender: TObject);
  323. begin
  324.   ListView.ViewStyle := TViewStyle((Sender as TComponent).Tag);
  325. end;
  326.  
  327. procedure TForm1.ListViewDblClick(Sender: TObject);
  328. var
  329.   RootPIDL,
  330.   ID: PItemIDList;
  331. begin
  332.   if ListView.Selected <> nil then
  333.   begin
  334.     ID := ShellItem(ListView.Selected.Index).ID;
  335.     if not IsFolder(FIShellFolder, ID) then Exit;
  336.     RootPIDL := ConcatPIDLs(FPIDL, ID);
  337.     SetPath(RootPIDL);
  338.   end;
  339. end;
  340.  
  341. function TForm1.ShellItem(Index: Integer): PShellItem;
  342. begin
  343.   Result := PShellItem(FIDList[Index]);
  344. end;
  345.  
  346. procedure TForm1.ListViewKeyDown(Sender: TObject; var Key: Word;
  347.   Shift: TShiftState);
  348. begin
  349.   case Key of
  350.     VK_RETURN:
  351.       ListViewDblClick(Sender);
  352.     VK_BACK:
  353.       btnBackClick(Sender);  
  354.   end;
  355. end;
  356.  
  357. //SHELL-RELATED ROUTINES.
  358.  
  359. procedure TForm1.ClearIDList;
  360. var
  361.   I: Integer;
  362. begin
  363.   for I := 0 to FIDList.Count-1 do
  364.   begin
  365.     DisposePIDL(ShellItem(I).ID);
  366.     Dispose(ShellItem(I));
  367.   end;
  368.   FIDList.Clear;
  369. end;
  370.  
  371. procedure TForm1.PopulateIDList(ShellFolder: IShellFolder);
  372. const
  373.   Flags = SHCONTF_FOLDERS or SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN;
  374. var
  375.   ID: PItemIDList;
  376.   EnumList: IEnumIDList;
  377.   NumIDs: LongWord;
  378.   SaveCursor: TCursor;
  379.   ShellItem: PShellItem;
  380. begin
  381.   SaveCursor := Screen.Cursor;
  382.   try
  383.     Screen.Cursor := crHourglass;
  384.     OleCheck(
  385.       ShellFolder.EnumObjects(
  386.         Application.Handle,
  387.         Flags,
  388.         EnumList)
  389.     );
  390.  
  391.     FIShellFolder := ShellFolder;
  392.     ClearIDList;
  393.     while EnumList.Next(1, ID, NumIDs) = S_OK do
  394.     begin
  395.       ShellItem := New(PShellItem);
  396.       ShellItem.ID := ID;
  397.       ShellItem.DisplayName := GetDisplayName(FIShellFolder, ID, False);
  398.       ShellItem.Empty := True;
  399.       FIDList.Add(ShellItem);
  400.     end;
  401.  
  402.     FIDList.Sort(ListSortFunc);
  403.  
  404.     //We need to tell the ListView how many items it has.
  405.     ListView.Items.Count := FIDList.Count;
  406.  
  407.     ListView.Repaint;
  408.   finally
  409.     Screen.Cursor := SaveCursor;
  410.   end;
  411. end;
  412.  
  413. procedure TForm1.SetPath(const Value: string);
  414. var
  415.   P: PWideChar;
  416.   NewPIDL: PItemIDList;
  417.   Flags,
  418.   NumChars: LongWord;
  419. begin
  420.   NumChars := Length(Value);
  421.   Flags := 0;
  422.   P := StringToOleStr(Value);
  423.  
  424.   OLECheck(
  425.     FIDesktopFolder.ParseDisplayName(
  426.       Application.Handle,
  427.       nil,
  428.       P,
  429.       NumChars,
  430.       NewPIDL,
  431.       Flags)
  432.    );
  433.   SetPath(NewPIDL);
  434. end;
  435.  
  436. procedure TForm1.SetPath(ID: PItemIDList);
  437. var
  438.   Index: Integer;
  439.   NewShellFolder: IShellFolder;
  440. begin
  441.    OLECheck(
  442.      FIDesktopFolder.BindToObject(
  443.             ID,
  444.             nil,
  445.             IID_IShellFolder,
  446.             Pointer(NewShellFolder))
  447.    );
  448.  
  449.   ListView.Items.BeginUpdate;
  450.   try
  451.     PopulateIDList(NewShellFolder);
  452.     FPIDL := ID;
  453.     FPath := GetDisplayName(FIDesktopFolder, FPIDL, True);
  454.     Index := cbPath.Items.IndexOf(FPath);
  455.     if (Index < 0) then
  456.     begin
  457.       cbPath.Items.InsertObject(0, FPath, Pointer(FPIDL));
  458.       cbPath.Text := cbPath.Items[0];
  459.     end
  460.     else begin
  461.       cbPath.ItemIndex := Index;
  462.       cbPath.Text := cbPath.Items[cbPath.ItemIndex];
  463.     end;
  464.  
  465.     if ListView.Items.Count > 0 then
  466.     begin
  467.       ListView.Selected := ListView.Items[0];
  468.       ListView.Selected.Focused := True;
  469.       ListView.Selected.MakeVisible(False);
  470.     end;
  471.   finally
  472.     ListView.Items.EndUpdate;
  473.   end;
  474. end;
  475.  
  476. //ROUTINES FOR MANAGING VIRTUAL DATA
  477.  
  478. procedure TForm1.CheckShellItems(StartIndex, EndIndex: Integer);
  479.  
  480.  function ValidFileTime(FileTime: TFileTime): Boolean;
  481.  begin
  482.    Result := (FileTime.dwLowDateTime <> 0) or (FileTime.dwHighDateTime <> 0);
  483.  end;
  484.  
  485. var
  486.   FileData: TWin32FindData;
  487.   FileInfo: TSHFileInfo;
  488.   SysTime: TSystemTime;
  489.   I: Integer;
  490.   LocalFileTime: TFILETIME;
  491. begin
  492.   //Here all the data that wasn't initialized in PopulateIDList is
  493.   //filled in.
  494.   for I := StartIndex to EndIndex do
  495.   begin
  496.     if ShellItem(I)^.Empty then
  497.     with ShellItem(I)^ do
  498.     begin
  499.       FullID := ConcatPIDLs(FPIDL, ID);
  500.       ImageIndex := GetShellImage(FullID, ListView.ViewStyle = vsIcon, False);
  501.  
  502.       //File Type
  503.       SHGetFileInfo(
  504.         PChar(FullID),
  505.         0,
  506.         FileInfo,
  507.         SizeOf(FileInfo),
  508.         SHGFI_TYPENAME or SHGFI_PIDL
  509.       );
  510.       TypeName := FileInfo.szTypeName;
  511.  
  512.       //Get File info from Windows
  513.       FillChar(FileData, SizeOf(FileData), #0);
  514.       SHGetDataFromIDList(
  515.         FIShellFolder,
  516.         ID,
  517.         SHGDFIL_FINDDATA,
  518.         @FileData,
  519.         SizeOf(FileData)
  520.       );
  521.  
  522.       //File Size, in KB
  523.       Size := FileData.nFileSizeLow div 1000;
  524.       if Size = 0 then Size := 1;
  525.  
  526.       //Modified Date
  527.       FillChar(LocalFileTime, SizeOf(TFileTime), #0);
  528.       with FileData do
  529.         if ValidFileTime(ftLastWriteTime)
  530.         and FileTimeToLocalFileTime(ftLastWriteTime, LocalFileTime)
  531.         and FileTimeToSystemTime(LocalFileTime, SysTime) then
  532.         try
  533.           ModDate := DateTimeToStr(SystemTimeToDateTime(SysTime))
  534.         except
  535.           on EConvertError do ModDate := '';
  536.         end
  537.         else
  538.           ModDate := '';
  539.  
  540.       //Attributes
  541.       Attributes := FileData.dwFileAttributes;
  542.  
  543.       //Flag this record as complete.
  544.       Empty := False;
  545.     end;
  546.   end;
  547. end;
  548.  
  549. procedure TForm1.ListViewDataHint(Sender: TObject; StartIndex,
  550.   EndIndex: Integer);
  551. begin
  552.   //OnDataHint is called before OnData. This gives you a chance to
  553.   //initialize only the data structures that need to be drawn.
  554.   //You should keep track of which items have been initialized so no
  555.   //extra work is done.
  556.   if (StartIndex > FIDList.Count) or (EndIndex > FIDList.Count) then Exit;
  557.   CheckShellItems(StartIndex, EndIndex);
  558. end;
  559.  
  560. procedure TForm1.ListViewData(Sender: TObject; Item: TListItem);
  561. var
  562.   Attrs: string;
  563. begin
  564.   //OnData gets called once for each item for which the ListView needs
  565.   //data. If the ListView is in Report View, be sure to add the subitems.
  566.   //Item is a "dummy" item whose only valid data is it's index which
  567.   //is used to index into the underlying data.
  568.   if (Item.Index > FIDList.Count) then Exit;
  569.   with ShellItem(Item.Index)^ do
  570.   begin
  571.     Item.Caption := DisplayName;
  572.     Item.ImageIndex := ImageIndex;
  573.  
  574.     if ListView.ViewStyle <> vsReport then Exit;
  575.  
  576.     if not IsFolder(FIShellFolder, ID) then
  577.       Item.SubItems.Add(Format('%dKB', [Size]))
  578.     else
  579.       Item.SubItems.Add('');
  580.     Item.SubItems.Add(TypeName);
  581.     try
  582.       Item.SubItems.Add(ModDate);
  583.     except
  584.     end;
  585.  
  586.     if Bool(Attributes and FILE_ATTRIBUTE_READONLY) then Attrs := Attrs + 'R';
  587.     if Bool(Attributes and FILE_ATTRIBUTE_HIDDEN) then Attrs := Attrs + 'H';
  588.     if Bool(Attributes and FILE_ATTRIBUTE_SYSTEM) then Attrs := Attrs + 'S';
  589.     if Bool(Attributes and FILE_ATTRIBUTE_ARCHIVE) then Attrs := Attrs + 'A';
  590.   end;
  591.   Item.SubItems.Add(Attrs);
  592. end;
  593.  
  594. procedure TForm1.ListViewDataFind(Sender: TObject; Find: TItemFind;
  595.   const FindString: String; const FindPosition: TPoint; FindData: Pointer;
  596.   StartIndex: Integer; Direction: TSearchDirection; Wrap: Boolean;
  597.   var Index: Integer);
  598. //OnDataFind gets called in response to calls to FindCaption, FindData,
  599. //GetNearestItem, etc. It also gets called for each keystroke sent to the
  600. //ListView (for incremental searching)
  601. var
  602.   I: Integer;
  603.   Found: Boolean;
  604. begin
  605.   I := StartIndex;
  606.   if (Find = ifExactString) or (Find = ifPartialString) then
  607.   begin
  608.     repeat
  609.       if (I = FIDList.Count-1) then
  610.         if Wrap then I := 0 else Exit;
  611.       Found := Pos(UpperCase(FindString), UpperCase(ShellItem(I)^.DisplayName)) = 1;
  612.       Inc(I);
  613.     until Found or (I = StartIndex);
  614.     if Found then Index := I-1;
  615.   end;
  616. end;
  617.  
  618. procedure TForm1.ListViewCustomDrawItem(Sender: TCustomListView;
  619.   Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
  620. var
  621.   Attrs: Integer;
  622. begin
  623.   if Item = nil then Exit;
  624.   Attrs := ShellItem(Item.Index).Attributes;
  625.   if Bool(Attrs and FILE_ATTRIBUTE_READONLY) then
  626.     ListView.Canvas.Font.Color := clGrayText;
  627.   if Bool(Attrs and FILE_ATTRIBUTE_HIDDEN) then
  628.     ListView.Canvas.Font.Style :=
  629.        ListView.Canvas.Font.Style + [fsStrikeOut];
  630.   if Bool(Attrs and FILE_ATTRIBUTE_SYSTEM) then
  631.     Listview.Canvas.Font.Color := clHighlight;
  632. end;
  633.  
  634. procedure TForm1.ListViewCustomDrawSubItem(Sender: TCustomListView;
  635.   Item: TListItem; SubItem: Integer; State: TCustomDrawState;
  636.   var DefaultDraw: Boolean);
  637. begin
  638.   if SubItem = 0 then Exit;
  639.   SubItem := SubItem-1;
  640.   ListView.Canvas.Font.Color := GetSysColor(COLOR_WINDOWTEXT);
  641.   //workaround for Win98 bug.
  642. end;
  643.  
  644. procedure TForm1.btnBackClick(Sender: TObject);
  645. var
  646.   Temp: PItemIDList;
  647. begin
  648.   Temp := CopyPIDL(FPIDL);
  649.   if Assigned(Temp) then
  650.     StripLastID(Temp);
  651.   if Temp.mkid.cb <> 0 then
  652.     SetPath(Temp)
  653.   else
  654.     Beep;
  655. end;
  656.  
  657. end.
  658.